/**
 * Copyright © 2023-2030 The ruanrongman Authors
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package top.rslly.iot.utility.ai.voice.concentus;

class Stereo {

  /// <summary>
  /// Decode mid/side predictors
  /// </summary>
  /// <param name="psRangeDec">I/O Compressor data structure</param>
  /// <param name="pred_Q13">O Predictors</param>
  static void silk_stereo_decode_pred(
      EntropyCoder psRangeDec,
      int[] pred_Q13) {
    int n;
    int[][] ix = Arrays.InitTwoDimensionalArrayInt(2, 3);
    int low_Q13, step_Q13;

    // Entropy decoding
    n = psRangeDec.dec_icdf(SilkTables.silk_stereo_pred_joint_iCDF, 8);
    ix[0][2] = Inlines.silk_DIV32_16(n, 5);
    ix[1][2] = n - 5 * ix[0][2];
    for (n = 0; n < 2; n++) {
      ix[n][0] = psRangeDec.dec_icdf(SilkTables.silk_uniform3_iCDF, 8);
      ix[n][1] = psRangeDec.dec_icdf(SilkTables.silk_uniform5_iCDF, 8);
    }

    // Dequantize
    for (n = 0; n < 2; n++) {
      ix[n][0] += 3 * ix[n][2];
      low_Q13 = SilkTables.silk_stereo_pred_quant_Q13[ix[n][0]];
      step_Q13 = Inlines.silk_SMULWB(SilkTables.silk_stereo_pred_quant_Q13[ix[n][0] + 1] - low_Q13,
          ((int) ((0.5f / SilkConstants.STEREO_QUANT_SUB_STEPS) * ((long) 1 << (16)) + 0.5))/*
                                                                                             * Inlines
                                                                                             * .
                                                                                             * SILK_CONST
                                                                                             * (0.5f
                                                                                             * /
                                                                                             * SilkConstants
                                                                                             * .
                                                                                             * STEREO_QUANT_SUB_STEPS,
                                                                                             * 16)
                                                                                             */);
      pred_Q13[n] = Inlines.silk_SMLABB(low_Q13, step_Q13, 2 * ix[n][1] + 1);
    }

    /* Subtract second from first predictor (helps when actually applying these) */
    pred_Q13[0] -= pred_Q13[1];
  }

  /// <summary>
  /// Decode mid-only flag
  /// </summary>
  /// <param name="psRangeDec">I/O Compressor data structure</param>
  /// <param name="decode_only_mid">O Flag that only mid channel has been coded</param>
  static void silk_stereo_decode_mid_only(
      EntropyCoder psRangeDec,
      BoxedValueInt decode_only_mid) {
    /* Decode flag that only mid channel is coded */
    decode_only_mid.Val = psRangeDec.dec_icdf(SilkTables.silk_stereo_only_code_mid_iCDF, 8);
  }

  /// <summary>
  /// Entropy code the mid/side quantization indices
  /// </summary>
  /// <param name="psRangeEnc">I/O Compressor data structure</param>
  /// <param name="ix">I Quantization indices [ 2 ][ 3 ]</param>
  static void silk_stereo_encode_pred(EntropyCoder psRangeEnc, byte[][] ix) {
    int n;

    /* Entropy coding */
    n = 5 * ix[0][2] + ix[1][2];
    Inlines.OpusAssert(n < 25);
    psRangeEnc.enc_icdf(n, SilkTables.silk_stereo_pred_joint_iCDF, 8);
    for (n = 0; n < 2; n++) {
      Inlines.OpusAssert(ix[n][0] < 3);
      Inlines.OpusAssert(ix[n][1] < SilkConstants.STEREO_QUANT_SUB_STEPS);
      psRangeEnc.enc_icdf(ix[n][0], SilkTables.silk_uniform3_iCDF, 8);
      psRangeEnc.enc_icdf(ix[n][1], SilkTables.silk_uniform5_iCDF, 8);
    }
  }

  /// <summary>
  /// Entropy code the mid-only flag
  /// </summary>
  /// <param name="psRangeEnc">I/O Compressor data structure</param>
  /// <param name="mid_only_flag"></param>
  static void silk_stereo_encode_mid_only(EntropyCoder psRangeEnc, byte mid_only_flag) {
    /* Encode flag that only mid channel is coded */
    psRangeEnc.enc_icdf(mid_only_flag, SilkTables.silk_stereo_only_code_mid_iCDF, 8);
  }

  /// <summary>
  /// Find least-squares prediction gain for one signal based on another and quantize it
  /// </summary>
  /// <param name="ratio_Q14">O Ratio of residual and mid energies</param>
  /// <param name="x">I Basis signal</param>
  /// <param name="y">I Target signal</param>
  /// <param name="mid_res_amp_Q0">I/O Smoothed mid, residual norms</param>
  /// <param name="length">I Number of samples</param>
  /// <param name="smooth_coef_Q16">I Smoothing coefficient</param>
  /// <returns>O Returns predictor in Q13</returns>
  static int silk_stereo_find_predictor(
      BoxedValueInt ratio_Q14,
      short[] x,
      short[] y,
      int[] mid_res_amp_Q0,
      int mid_res_amp_Q0_ptr,
      int length,
      int smooth_coef_Q16) {
    int scale;
    // fixme optimize this unboxing
    BoxedValueInt nrgx = new BoxedValueInt(0);
    BoxedValueInt nrgy = new BoxedValueInt(0);
    BoxedValueInt scale1 = new BoxedValueInt(0);
    BoxedValueInt scale2 = new BoxedValueInt(0);
    int corr, pred_Q13, pred2_Q10;

    /* Find predictor */
    SumSqrShift.silk_sum_sqr_shift(nrgx, scale1, x, length);
    SumSqrShift.silk_sum_sqr_shift(nrgy, scale2, y, length);
    scale = Inlines.silk_max_int(scale1.Val, scale2.Val);
    scale = scale + (scale & 1);
    /* make even */
    nrgy.Val = Inlines.silk_RSHIFT32(nrgy.Val, scale - scale2.Val);
    nrgx.Val = Inlines.silk_RSHIFT32(nrgx.Val, scale - scale1.Val);
    nrgx.Val = Inlines.silk_max_int(nrgx.Val, 1);
    corr = Inlines.silk_inner_prod_aligned_scale(x, y, scale, length);
    pred_Q13 = Inlines.silk_DIV32_varQ(corr, nrgx.Val, 13);
    pred_Q13 = Inlines.silk_LIMIT(pred_Q13, -(1 << 14), 1 << 14);
    pred2_Q10 = Inlines.silk_SMULWB(pred_Q13, pred_Q13);

    /* Faster update for signals with large prediction parameters */
    smooth_coef_Q16 = (int) Inlines.silk_max_int(smooth_coef_Q16, Inlines.silk_abs(pred2_Q10));

    /* Smoothed mid and residual norms */
    Inlines.OpusAssert(smooth_coef_Q16 < 32768);
    scale = Inlines.silk_RSHIFT(scale, 1);
    mid_res_amp_Q0[mid_res_amp_Q0_ptr] = Inlines.silk_SMLAWB(mid_res_amp_Q0[mid_res_amp_Q0_ptr],
        Inlines.silk_LSHIFT(Inlines.silk_SQRT_APPROX(nrgx.Val), scale)
            - mid_res_amp_Q0[mid_res_amp_Q0_ptr],
        smooth_coef_Q16);
    /* Residual energy = nrgy - 2 * pred * corr + pred^2 * nrgx */
    nrgy.Val = Inlines.silk_SUB_LSHIFT32(nrgy.Val, Inlines.silk_SMULWB(corr, pred_Q13), 3 + 1);
    nrgy.Val = Inlines.silk_ADD_LSHIFT32(nrgy.Val, Inlines.silk_SMULWB(nrgx.Val, pred2_Q10), 6);
    mid_res_amp_Q0[mid_res_amp_Q0_ptr + 1] =
        Inlines.silk_SMLAWB(mid_res_amp_Q0[mid_res_amp_Q0_ptr + 1],
            Inlines.silk_LSHIFT(Inlines.silk_SQRT_APPROX(nrgy.Val), scale)
                - mid_res_amp_Q0[mid_res_amp_Q0_ptr + 1],
            smooth_coef_Q16);

    /* Ratio of smoothed residual and mid norms */
    ratio_Q14.Val = Inlines.silk_DIV32_varQ(mid_res_amp_Q0[mid_res_amp_Q0_ptr + 1],
        Inlines.silk_max(mid_res_amp_Q0[mid_res_amp_Q0_ptr], 1), 14);
    ratio_Q14.Val = Inlines.silk_LIMIT(ratio_Q14.Val, 0, 32767);

    return pred_Q13;
  }

  /// <summary>
  /// Convert Left/Right stereo signal to adaptive Mid/Side representation
  /// </summary>
  /// <param name="state">I/O State</param>
  /// <param name="x1">I/O Left input signal, becomes mid signal</param>
  /// <param name="x2">I/O Right input signal, becomes side signal</param>
  /// <param name="ix">O Quantization indices [ 2 ][ 3 ]</param>
  /// <param name="mid_only_flag">O Flag: only mid signal coded</param>
  /// <param name="mid_side_rates_bps">O Bitrates for mid and side signals</param>
  /// <param name="total_rate_bps">I Total bitrate</param>
  /// <param name="prev_speech_act_Q8">I Speech activity level in previous frame</param>
  /// <param name="toMono">I Last frame before a stereo.mono transition</param>
  /// <param name="fs_kHz">I Sample rate (kHz)</param>
  /// <param name="frame_length">I Number of samples</param>
  static void silk_stereo_LR_to_MS(
      StereoEncodeState state,
      short[] x1,
      int x1_ptr,
      short[] x2,
      int x2_ptr,
      byte[][] ix,
      BoxedValueByte mid_only_flag,
      int[] mid_side_rates_bps,
      int total_rate_bps,
      int prev_speech_act_Q8,
      int toMono,
      int fs_kHz,
      int frame_length) {
    int n, is10msFrame, denom_Q16, delta0_Q13, delta1_Q13;
    int sum, diff, smooth_coef_Q16, pred0_Q13, pred1_Q13;
    int[] pred_Q13 = new int[2];
    int frac_Q16, frac_3_Q16, min_mid_rate_bps, width_Q14, w_Q24, deltaw_Q24;
    BoxedValueInt LP_ratio_Q14 = new BoxedValueInt(0);
    BoxedValueInt HP_ratio_Q14 = new BoxedValueInt(0);
    short[] side;
    short[] LP_mid;
    short[] HP_mid;
    short[] LP_side;
    short[] HP_side;
    int mid = x1_ptr - 2;

    side = new short[frame_length + 2];

    /* Convert to basic mid/side signals */
    for (n = 0; n < frame_length + 2; n++) {
      sum = x1[x1_ptr + n - 2] + (int) x2[x2_ptr + n - 2];
      diff = x1[x1_ptr + n - 2] - (int) x2[x2_ptr + n - 2];
      x1[mid + n] = (short) Inlines.silk_RSHIFT_ROUND(sum, 1);
      side[n] = (short) Inlines.silk_SAT16(Inlines.silk_RSHIFT_ROUND(diff, 1));
    }

    /* Buffering */
    System.arraycopy(state.sMid, 0, x1, mid, 2);
    System.arraycopy(state.sSide, 0, side, 0, 2);
    System.arraycopy(x1, mid + frame_length, state.sMid, 0, 2);
    System.arraycopy(side, frame_length, state.sSide, 0, 2);

    /* LP and HP filter mid signal */
    LP_mid = new short[frame_length];
    HP_mid = new short[frame_length];
    for (n = 0; n < frame_length; n++) {
      sum = Inlines.silk_RSHIFT_ROUND(
          Inlines.silk_ADD_LSHIFT32(x1[mid + n] + x1[mid + n + 2], x1[mid + n + 1], 1), 2);
      LP_mid[n] = (short) (sum);
      HP_mid[n] = (short) (x1[mid + n + 1] - sum);
    }

    /* LP and HP filter side signal */
    LP_side = new short[frame_length];
    HP_side = new short[frame_length];
    for (n = 0; n < frame_length; n++) {
      sum = Inlines
          .silk_RSHIFT_ROUND(Inlines.silk_ADD_LSHIFT32(side[n] + side[n + 2], side[n + 1], 1), 2);
      LP_side[n] = (short) (sum);
      HP_side[n] = (short) (side[n + 1] - sum);
    }

    /* Find energies and predictors */
    is10msFrame = (frame_length == 10 * fs_kHz ? 1 : 0);
    smooth_coef_Q16 = is10msFrame != 0
        ? ((int) ((SilkConstants.STEREO_RATIO_SMOOTH_COEF / 2) * ((long) 1 << (16)) + 0.5))
        /* Inlines.SILK_CONST(SilkConstants.STEREO_RATIO_SMOOTH_COEF / 2, 16) */ : ((int) ((SilkConstants.STEREO_RATIO_SMOOTH_COEF)
            * ((long) 1 << (16)) + 0.5))/*
                                         * Inlines.SILK_CONST(SilkConstants.
                                         * STEREO_RATIO_SMOOTH_COEF, 16)
                                         */;
    smooth_coef_Q16 = Inlines
        .silk_SMULWB(Inlines.silk_SMULBB(prev_speech_act_Q8, prev_speech_act_Q8), smooth_coef_Q16);

    pred_Q13[0] = silk_stereo_find_predictor(LP_ratio_Q14, LP_mid, LP_side, state.mid_side_amp_Q0,
        0, frame_length, smooth_coef_Q16);
    pred_Q13[1] = silk_stereo_find_predictor(HP_ratio_Q14, HP_mid, HP_side, state.mid_side_amp_Q0,
        2, frame_length, smooth_coef_Q16);

    /* Ratio of the norms of residual and mid signals */
    frac_Q16 = Inlines.silk_SMLABB(HP_ratio_Q14.Val, LP_ratio_Q14.Val, 3);
    frac_Q16 = Inlines.silk_min(frac_Q16, ((int) ((1) * ((long) 1 << (16)) + 0.5))/*
                                                                                   * Inlines.
                                                                                   * SILK_CONST(1,
                                                                                   * 16)
                                                                                   */);

    /* Determine bitrate distribution between mid and side, and possibly reduce stereo width */
    total_rate_bps -= is10msFrame != 0 ? 1200 : 600;
    /* Subtract approximate bitrate for coding stereo parameters */
    if (total_rate_bps < 1) {
      total_rate_bps = 1;
    }
    min_mid_rate_bps = Inlines.silk_SMLABB(2000, fs_kHz, 900);
    Inlines.OpusAssert(min_mid_rate_bps < 32767);
    /*
     * Default bitrate distribution: 8 parts for Mid and (5+3*frac) parts for Side. so: mid_rate = (
     * 8 / ( 13 + 3 * frac ) ) * total_ rate
     */
    frac_3_Q16 = Inlines.silk_MUL(3, frac_Q16);
    mid_side_rates_bps[0] =
        Inlines.silk_DIV32_varQ(total_rate_bps, ((int) ((8 + 5) * ((long) 1 << (16)) + 0.5))
            /* Inlines.SILK_CONST(8 + 5, 16) */ + frac_3_Q16, 16 + 3);
    /* If Mid bitrate below minimum, reduce stereo width */
    if (mid_side_rates_bps[0] < min_mid_rate_bps) {
      mid_side_rates_bps[0] = min_mid_rate_bps;
      mid_side_rates_bps[1] = total_rate_bps - mid_side_rates_bps[0];
      /* width = 4 * ( 2 * side_rate - min_rate ) / ( ( 1 + 3 * frac ) * min_rate ) */
      width_Q14 = Inlines.silk_DIV32_varQ(
          Inlines.silk_LSHIFT(mid_side_rates_bps[1], 1) - min_mid_rate_bps,
          Inlines.silk_SMULWB(
              ((int) ((1) * ((long) 1 << (16)) + 0.5))/* Inlines.SILK_CONST(1, 16) */ + frac_3_Q16,
              min_mid_rate_bps),
          14 + 2);
      width_Q14 = Inlines.silk_LIMIT(width_Q14, 0, ((int) ((1) * ((long) 1 << (14)) + 0.5))/*
                                                                                            * Inlines
                                                                                            * .
                                                                                            * SILK_CONST
                                                                                            * (1,
                                                                                            * 14)
                                                                                            */);
    } else {
      mid_side_rates_bps[1] = total_rate_bps - mid_side_rates_bps[0];
      width_Q14 = ((int) ((1) * ((long) 1 << (14)) + 0.5))/* Inlines.SILK_CONST(1, 14) */;
    }

    /* Smoother */
    state.smth_width_Q14 = (short) Inlines.silk_SMLAWB(state.smth_width_Q14,
        width_Q14 - state.smth_width_Q14, smooth_coef_Q16);

    /*
     * At very low bitrates or for inputs that are nearly amplitude panned, switch to panned-mono
     * coding
     */
    mid_only_flag.Val = 0;
    if (toMono != 0) {
      /* Last frame before stereo.mono transition; collapse stereo width */
      width_Q14 = 0;
      pred_Q13[0] = 0;
      pred_Q13[1] = 0;
      silk_stereo_quant_pred(pred_Q13, ix);
    } else if (state.width_prev_Q14 == 0
        && (8 * total_rate_bps < 13 * min_mid_rate_bps || Inlines
            .silk_SMULWB(frac_Q16, state.smth_width_Q14) < ((int) ((0.05f) * ((long) 1 << (14))
                + 0.5))/* Inlines.SILK_CONST(0.05f, 14) */)) {
      /* Code as panned-mono; previous frame already had zero width */
      /* Scale down and quantize predictors */
      pred_Q13[0] = Inlines.silk_RSHIFT(Inlines.silk_SMULBB(state.smth_width_Q14, pred_Q13[0]), 14);
      pred_Q13[1] = Inlines.silk_RSHIFT(Inlines.silk_SMULBB(state.smth_width_Q14, pred_Q13[1]), 14);
      silk_stereo_quant_pred(pred_Q13, ix);
      /* Collapse stereo width */
      width_Q14 = 0;
      pred_Q13[0] = 0;
      pred_Q13[1] = 0;
      mid_side_rates_bps[0] = total_rate_bps;
      mid_side_rates_bps[1] = 0;
      mid_only_flag.Val = 1;
    } else if (state.width_prev_Q14 != 0
        && (8 * total_rate_bps < 11 * min_mid_rate_bps || Inlines
            .silk_SMULWB(frac_Q16, state.smth_width_Q14) < ((int) ((0.02f) * ((long) 1 << (14))
                + 0.5))/* Inlines.SILK_CONST(0.02f, 14) */)) {
      /* Transition to zero-width stereo */
      /* Scale down and quantize predictors */
      pred_Q13[0] = Inlines.silk_RSHIFT(Inlines.silk_SMULBB(state.smth_width_Q14, pred_Q13[0]), 14);
      pred_Q13[1] = Inlines.silk_RSHIFT(Inlines.silk_SMULBB(state.smth_width_Q14, pred_Q13[1]), 14);
      silk_stereo_quant_pred(pred_Q13, ix);
      /* Collapse stereo width */
      width_Q14 = 0;
      pred_Q13[0] = 0;
      pred_Q13[1] = 0;
    } else if (state.smth_width_Q14 > ((int) ((0.95f) * ((long) 1 << (14)) + 0.5))/*
                                                                                   * Inlines.
                                                                                   * SILK_CONST(0.
                                                                                   * 95f, 14)
                                                                                   */) {
      /* Full-width stereo coding */
      silk_stereo_quant_pred(pred_Q13, ix);
      width_Q14 = ((int) ((1) * ((long) 1 << (14)) + 0.5))/* Inlines.SILK_CONST(1, 14) */;
    } else {
      /* Reduced-width stereo coding; scale down and quantize predictors */
      pred_Q13[0] = Inlines.silk_RSHIFT(Inlines.silk_SMULBB(state.smth_width_Q14, pred_Q13[0]), 14);
      pred_Q13[1] = Inlines.silk_RSHIFT(Inlines.silk_SMULBB(state.smth_width_Q14, pred_Q13[1]), 14);
      silk_stereo_quant_pred(pred_Q13, ix);
      width_Q14 = state.smth_width_Q14;
    }

    /* Make sure to keep on encoding until the tapered output has been transmitted */
    if (mid_only_flag.Val == 1) {
      state.silent_side_len += (short) (frame_length - SilkConstants.STEREO_INTERP_LEN_MS * fs_kHz);
      if (state.silent_side_len < SilkConstants.LA_SHAPE_MS * fs_kHz) {
        mid_only_flag.Val = 0;
      } else {
        /* Limit to avoid wrapping around */
        state.silent_side_len = 10000;
      }
    } else {
      state.silent_side_len = 0;
    }

    if (mid_only_flag.Val == 0 && mid_side_rates_bps[1] < 1) {
      mid_side_rates_bps[1] = 1;
      mid_side_rates_bps[0] = Inlines.silk_max_int(1, total_rate_bps - mid_side_rates_bps[1]);
    }

    /* Interpolate predictors and subtract prediction from side channel */
    pred0_Q13 = -state.pred_prev_Q13[0];
    pred1_Q13 = -state.pred_prev_Q13[1];
    w_Q24 = Inlines.silk_LSHIFT(state.width_prev_Q14, 10);
    denom_Q16 = Inlines.silk_DIV32_16((int) 1 << 16, SilkConstants.STEREO_INTERP_LEN_MS * fs_kHz);
    delta0_Q13 = 0 - Inlines.silk_RSHIFT_ROUND(
        Inlines.silk_SMULBB(pred_Q13[0] - state.pred_prev_Q13[0], denom_Q16), 16);
    delta1_Q13 = 0 - Inlines.silk_RSHIFT_ROUND(
        Inlines.silk_SMULBB(pred_Q13[1] - state.pred_prev_Q13[1], denom_Q16), 16);
    deltaw_Q24 =
        Inlines.silk_LSHIFT(Inlines.silk_SMULWB(width_Q14 - state.width_prev_Q14, denom_Q16), 10);
    for (n = 0; n < SilkConstants.STEREO_INTERP_LEN_MS * fs_kHz; n++) {
      pred0_Q13 += delta0_Q13;
      pred1_Q13 += delta1_Q13;
      w_Q24 += deltaw_Q24;
      sum = Inlines.silk_LSHIFT(
          Inlines.silk_ADD_LSHIFT(x1[mid + n] + x1[mid + n + 2], x1[mid + n + 1], 1), 9);
      /* Q11 */
      sum = Inlines.silk_SMLAWB(Inlines.silk_SMULWB(w_Q24, side[n + 1]), sum, pred0_Q13);
      /* Q8 */
      sum = Inlines.silk_SMLAWB(sum, Inlines.silk_LSHIFT((int) x1[mid + n + 1], 11), pred1_Q13);
      /* Q8 */
      x2[x2_ptr + n - 1] = (short) Inlines.silk_SAT16(Inlines.silk_RSHIFT_ROUND(sum, 8));
    }

    pred0_Q13 = 0 - pred_Q13[0];
    pred1_Q13 = 0 - pred_Q13[1];
    w_Q24 = Inlines.silk_LSHIFT(width_Q14, 10);
    for (n = SilkConstants.STEREO_INTERP_LEN_MS * fs_kHz; n < frame_length; n++) {
      sum = Inlines.silk_LSHIFT(
          Inlines.silk_ADD_LSHIFT(x1[mid + n] + x1[mid + n + 2], x1[mid + n + 1], 1), 9);
      /* Q11 */
      sum = Inlines.silk_SMLAWB(Inlines.silk_SMULWB(w_Q24, side[n + 1]), sum, pred0_Q13);
      /* Q8 */
      sum = Inlines.silk_SMLAWB(sum, Inlines.silk_LSHIFT((int) x1[mid + n + 1], 11), pred1_Q13);
      /* Q8 */
      x2[x2_ptr + n - 1] = (short) Inlines.silk_SAT16(Inlines.silk_RSHIFT_ROUND(sum, 8));
    }
    state.pred_prev_Q13[0] = (short) pred_Q13[0];
    state.pred_prev_Q13[1] = (short) pred_Q13[1];
    state.width_prev_Q14 = (short) width_Q14;
  }

  /// <summary>
  /// Convert adaptive Mid/Side representation to Left/Right stereo signal
  /// </summary>
  /// <param name="state">I/O State</param>
  /// <param name="x1">I/O Left input signal, becomes mid signal</param>
  /// <param name="x2">I/O Right input signal, becomes side signal</param>
  /// <param name="pred_Q13">I Predictors</param>
  /// <param name="fs_kHz">I Samples rate (kHz)</param>
  /// <param name="frame_length">I Number of samples</param>
  static void silk_stereo_MS_to_LR(
      StereoDecodeState state,
      short[] x1,
      int x1_ptr,
      short[] x2,
      int x2_ptr,
      int[] pred_Q13,
      int fs_kHz,
      int frame_length) {
    int n, denom_Q16, delta0_Q13, delta1_Q13;
    int sum, diff, pred0_Q13, pred1_Q13;

    /* Buffering */
    System.arraycopy(state.sMid, 0, x1, x1_ptr, 2);
    System.arraycopy(state.sSide, 0, x2, x2_ptr, 2);
    System.arraycopy(x1, x1_ptr + frame_length, state.sMid, 0, 2);
    System.arraycopy(x2, x2_ptr + frame_length, state.sSide, 0, 2);

    /* Interpolate predictors and add prediction to side channel */
    pred0_Q13 = state.pred_prev_Q13[0];
    pred1_Q13 = state.pred_prev_Q13[1];
    denom_Q16 = Inlines.silk_DIV32_16((int) 1 << 16, SilkConstants.STEREO_INTERP_LEN_MS * fs_kHz);
    delta0_Q13 = Inlines.silk_RSHIFT_ROUND(
        Inlines.silk_SMULBB(pred_Q13[0] - state.pred_prev_Q13[0], denom_Q16), 16);
    delta1_Q13 = Inlines.silk_RSHIFT_ROUND(
        Inlines.silk_SMULBB(pred_Q13[1] - state.pred_prev_Q13[1], denom_Q16), 16);
    for (n = 0; n < SilkConstants.STEREO_INTERP_LEN_MS * fs_kHz; n++) {
      pred0_Q13 += delta0_Q13;
      pred1_Q13 += delta1_Q13;
      sum = Inlines.silk_LSHIFT(
          Inlines.silk_ADD_LSHIFT(x1[x1_ptr + n] + x1[x1_ptr + n + 2], x1[x1_ptr + n + 1], 1), 9);
      /* Q11 */
      sum = Inlines.silk_SMLAWB(Inlines.silk_LSHIFT((int) x2[x2_ptr + n + 1], 8), sum, pred0_Q13);
      /* Q8 */
      sum = Inlines.silk_SMLAWB(sum, Inlines.silk_LSHIFT((int) x1[x1_ptr + n + 1], 11), pred1_Q13);
      /* Q8 */
      x2[x2_ptr + n + 1] = (short) Inlines.silk_SAT16(Inlines.silk_RSHIFT_ROUND(sum, 8));
    }
    pred0_Q13 = pred_Q13[0];
    pred1_Q13 = pred_Q13[1];
    for (n = SilkConstants.STEREO_INTERP_LEN_MS * fs_kHz; n < frame_length; n++) {
      sum = Inlines.silk_LSHIFT(
          Inlines.silk_ADD_LSHIFT(x1[x1_ptr + n] + x1[x1_ptr + n + 2], x1[x1_ptr + n + 1], 1), 9);
      /* Q11 */
      sum = Inlines.silk_SMLAWB(Inlines.silk_LSHIFT((int) x2[x2_ptr + n + 1], 8), sum, pred0_Q13);
      /* Q8 */
      sum = Inlines.silk_SMLAWB(sum, Inlines.silk_LSHIFT((int) x1[x1_ptr + n + 1], 11), pred1_Q13);
      /* Q8 */
      x2[x2_ptr + n + 1] = (short) Inlines.silk_SAT16(Inlines.silk_RSHIFT_ROUND(sum, 8));
    }
    state.pred_prev_Q13[0] = (short) (pred_Q13[0]);
    state.pred_prev_Q13[1] = (short) (pred_Q13[1]);

    /* Convert to left/right signals */
    for (n = 0; n < frame_length; n++) {
      sum = x1[x1_ptr + n + 1] + (int) x2[x2_ptr + n + 1];
      diff = x1[x1_ptr + n + 1] - (int) x2[x2_ptr + n + 1];
      x1[x1_ptr + n + 1] = (short) Inlines.silk_SAT16(sum);
      x2[x2_ptr + n + 1] = (short) Inlines.silk_SAT16(diff);
    }
  }

  /// <summary>
  /// Quantize mid/side predictors
  /// </summary>
  /// <param name="pred_Q13">I/O Predictors (out: quantized)</param>
  /// <param name="ix">O Quantization indices [ 2 ][ 3 ]</param>
  static void silk_stereo_quant_pred(
      int[] pred_Q13,
      byte[][] ix) {
    byte i, j; // [porting note] these were originally ints
    int n;
    int low_Q13, step_Q13, lvl_Q13, err_min_Q13, err_Q13, quant_pred_Q13 = 0;

    // FIXME: ix was formerly an out parameter that was newly allocated here
    // but now it relies on the caller to initialize it
    // clear ix
    Arrays.MemSet(ix[0], (byte) 0, 3);
    Arrays.MemSet(ix[1], (byte) 0, 3);

    /* Quantize */
    for (n = 0; n < 2; n++) {
      boolean done = false;

      /* Brute-force search over quantization levels */
      err_min_Q13 = Integer.MAX_VALUE;
      for (i = 0; !done && i < SilkConstants.STEREO_QUANT_TAB_SIZE - 1; i++) {
        low_Q13 = SilkTables.silk_stereo_pred_quant_Q13[i];
        step_Q13 = Inlines.silk_SMULWB(SilkTables.silk_stereo_pred_quant_Q13[i + 1] - low_Q13,
            ((int) ((0.5f / SilkConstants.STEREO_QUANT_SUB_STEPS) * ((long) 1 << (16)) + 0.5))/*
                                                                                               * Inlines
                                                                                               * .
                                                                                               * SILK_CONST
                                                                                               * (0.
                                                                                               * 5f
                                                                                               * /
                                                                                               * SilkConstants
                                                                                               * .
                                                                                               * STEREO_QUANT_SUB_STEPS,
                                                                                               * 16)
                                                                                               */);

        for (j = 0; !done && j < SilkConstants.STEREO_QUANT_SUB_STEPS; j++) {
          lvl_Q13 = Inlines.silk_SMLABB(low_Q13, step_Q13, 2 * j + 1);
          err_Q13 = Inlines.silk_abs(pred_Q13[n] - lvl_Q13);
          if (err_Q13 < err_min_Q13) {
            err_min_Q13 = err_Q13;
            quant_pred_Q13 = lvl_Q13;
            ix[n][0] = i;
            ix[n][1] = j;
          } else {
            /* Error increasing, so we're past the optimum */
            done = true;
          }
        }
      }

      ix[n][2] = (byte) (Inlines.silk_DIV32_16(ix[n][0], 3));
      ix[n][0] = (byte) (ix[n][0] - (byte) (ix[n][2] * 3));
      pred_Q13[n] = quant_pred_Q13;
    }

    /* Subtract second from first predictor (helps when actually applying these) */
    pred_Q13[0] -= pred_Q13[1];
  }
}
